package sc.server;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

public class RequestBuffer implements HttpServletRequest, Serializable {

	/**
	 * Serial UID.
	 */
	private static final long serialVersionUID = 1L;

	private final class ContentInputStream extends ServletInputStream {
		ByteArrayInputStream bais;

		public ContentInputStream() {
			bais = new ByteArrayInputStream(content);
		}

		@Override
		public int read() throws IOException {
			return bais.read();
		}
	}

	private Map<String, Object> attributes;
	private String encoding;
	private Set<Locale> locales;
	private byte[] content;
	private String contentType;
	private String localAddr;
	private String localName;
	private int localPort;
	private Map<String, String[]> parameters;
	private String protocol;
	private String remoteAddr;
	private String remoteHost;
	private int remotePort;
	private String scheme;
	private String serverName;
	private int serverPort;
	private boolean isSecure;
	private String authType;
	private String contextPath;
	private Cookie[] cookies;
	private Map<String, String[]> headers;
	private String method;
	private String pathInfo;
	private String pathTranslated;
	private String queryString;
	private String remoteUser;
	private String requestUri;
	private String sessionId;
	private String servletPath;
	private boolean isSessionIdFromCookie;
	private boolean isSessionIdFromUrl;
	private boolean isSessionIdValid;

	public RequestBuffer(HttpServletRequest request) throws IOException {
		attributes = convertAttributes(request);
		encoding = request.getCharacterEncoding();
		locales = toSet(request.getLocales(), Locale.class);
		contentType = request.getContentType();
		localAddr = request.getLocalAddr();
		localName = request.getLocalName();
		localPort = request.getLocalPort();
		parameters = convertParameters(request);
		protocol = request.getProtocol();
		remoteAddr = request.getRemoteAddr();
		remoteHost = request.getRemoteHost();
		remotePort = request.getRemotePort();
		scheme = request.getScheme();
		serverName = request.getServerName();
		serverPort = request.getServerPort();
		isSecure = request.isSecure();
		authType = request.getAuthType();
		contextPath = request.getContextPath();
		cookies = request.getCookies();
		headers = convertHeaders(request);
		method = request.getMethod();
		pathInfo = request.getPathInfo();
		pathTranslated = request.getPathTranslated();
		queryString = request.getQueryString();
		remoteUser = request.getRemoteUser();
		requestUri = request.getRequestURI();
		sessionId = request.getRequestedSessionId();
		servletPath = request.getServletPath();
		isSessionIdFromCookie = request.isRequestedSessionIdFromCookie();
		isSessionIdFromUrl = request.isRequestedSessionIdFromURL();
		isSessionIdValid = request.isRequestedSessionIdValid();
		content = getContent(request);
	}

	private Map<String, String[]> convertHeaders(HttpServletRequest request) {
		Map<String, String[]> headers = new HashMap<String, String[]>();
		Enumeration<?> names = request.getHeaderNames();
		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			Enumeration<?> values = request.getHeaders(name);
			List<String> valueList = new ArrayList<String>();
			while (values.hasMoreElements()) {
				String value = (String) values.nextElement();
				valueList.add(value);
			}
			String[] valueArray = new String[valueList.size()];
			valueArray = valueList.toArray(valueArray);
			headers.put(name, valueArray);
		}
		return headers;
	}

	private Map<String, String[]> convertParameters(HttpServletRequest request) {
		Map<String, String[]> parameters = new HashMap<String, String[]>();
		Enumeration<?> names = request.getParameterNames();
		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			String[] values = request.getParameterValues(name);
			parameters.put(name, values);
		}
		return parameters;
	}

	private byte[] getContent(HttpServletRequest request) throws IOException {
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		InputStream is = request.getInputStream();
		byte[] buffer = new byte[1024];
		int c;
		while ((c = is.read(buffer)) != -1) {
			baos.write(buffer, 0, c);
		}
		return baos.toByteArray();
	}

	private <T> Set<T> toSet(Enumeration<?> e, Class<T> cls) {
		Set<T> set = new HashSet<T>();
		while (e.hasMoreElements()) {
			Object next = e.nextElement();
			if (cls.isInstance(next)) {
				set.add(cls.cast(next));
			}
		}
		return set;
	}

	private Map<String, Object> convertAttributes(HttpServletRequest request) {
		Map<String, Object> map = new HashMap<String, Object>();
		Enumeration<?> names = request.getAttributeNames();
		while (names.hasMoreElements()) {
			String name = (String) names.nextElement();
			map.put(name, request.getAttribute(name));
		}
		return map;
	}

	@Override
	public Object getAttribute(String name) {
		return attributes.get(name);
	}

	@Override
	public Enumeration<?> getAttributeNames() {
		return Collections.enumeration(attributes.keySet());
	}

	@Override
	public String getCharacterEncoding() {
		return encoding;
	}

	@Override
	public int getContentLength() {
		return content.length;
	}

	@Override
	public String getContentType() {
		return contentType;
	}

	@Override
	public ServletInputStream getInputStream() throws IOException {
		return new ContentInputStream();
	}

	@Override
	public String getLocalAddr() {
		return localAddr;
	}

	@Override
	public String getLocalName() {
		return localName;
	}

	@Override
	public int getLocalPort() {
		return localPort;
	}

	@Override
	public Locale getLocale() {
		Locale locale = null;
		if (locales.size() > 0) {
			locale = locales.iterator().next();
		}
		return locale;
	}

	@Override
	public Enumeration<?> getLocales() {
		return Collections.enumeration(locales);
	}

	@Override
	public String getParameter(String name) {
		return commaSeparate(parameters.get(name));
	}

	@Override
	public Map<?, ?> getParameterMap() {
		return parameters;
	}

	@Override
	public Enumeration<?> getParameterNames() {
		return Collections.enumeration(parameters.keySet());
	}

	@Override
	public String[] getParameterValues(String name) {
		return parameters.get(name);
	}

	@Override
	public String getProtocol() {
		return protocol;
	}

	@Override
	public BufferedReader getReader() throws IOException {
		return new BufferedReader(new InputStreamReader(getInputStream(),
				encoding));
	}

	@Override
	public String getRealPath(String path) {
		return null;
	}

	@Override
	public String getRemoteAddr() {
		return remoteAddr;
	}

	@Override
	public String getRemoteHost() {
		return remoteHost;
	}

	@Override
	public int getRemotePort() {
		return remotePort;
	}

	@Override
	public RequestDispatcher getRequestDispatcher(String arg0) {
		return null;
	}

	@Override
	public String getScheme() {
		return scheme;
	}

	@Override
	public String getServerName() {
		return serverName;
	}

	@Override
	public int getServerPort() {
		return serverPort;
	}

	@Override
	public boolean isSecure() {
		return isSecure;
	}

	@Override
	public void removeAttribute(String name) {
		attributes.remove(name);
	}

	@Override
	public void setAttribute(String name, Object value) {
		attributes.put(name, value);
	}

	@Override
	public void setCharacterEncoding(String encoding)
			throws UnsupportedEncodingException {
		this.encoding = encoding;
	}

	@Override
	public String getAuthType() {
		return authType;
	}

	@Override
	public String getContextPath() {
		return contextPath;
	}

	@Override
	public Cookie[] getCookies() {
		return cookies;
	}

	@Override
	public long getDateHeader(String name) {
		int value = -1;
		Object valueObject = headers.get(name);
		if (valueObject != null) {
			// TODO conversion
		}
		return 0;
	}

	@Override
	public String getHeader(String name) {
		return commaSeparate(headers.get(name));
	}

	@Override
	public Enumeration<?> getHeaderNames() {
		return Collections.enumeration(headers.keySet());
	}

	@Override
	public Enumeration<?> getHeaders(String name) {
		return Collections.enumeration(Arrays.asList(headers.get(name)));
	}

	@Override
	public int getIntHeader(String name) {
		int value = -1;
		String[] header = headers.get(name);
		if (header != null && header.length != 0) {
			try {
				value = Integer.parseInt(header[0]);
			} catch (NumberFormatException e) {
			}
		}
		return value;
	}

	@Override
	public String getMethod() {
		return method;
	}

	@Override
	public String getPathInfo() {
		return pathInfo;
	}

	@Override
	public String getPathTranslated() {
		return pathTranslated;
	}

	@Override
	public String getQueryString() {
		return queryString;
	}

	@Override
	public String getRemoteUser() {
		return remoteUser;
	}

	@Override
	public String getRequestURI() {
		return requestUri;
	}

	@Override
	public StringBuffer getRequestURL() {
		return new StringBuffer(requestUri);
	}

	@Override
	public String getRequestedSessionId() {
		return sessionId;
	}

	@Override
	public String getServletPath() {
		return servletPath;
	}

	@Override
	public HttpSession getSession() {
		return null;
	}

	@Override
	public HttpSession getSession(boolean arg0) {
		return null;
	}

	@Override
	public Principal getUserPrincipal() {
		return null;
	}

	@Override
	public boolean isRequestedSessionIdFromCookie() {
		return isSessionIdFromCookie;
	}

	@Override
	public boolean isRequestedSessionIdFromURL() {
		return isSessionIdFromUrl;
	}

	@Override
	public boolean isRequestedSessionIdFromUrl() {
		return isSessionIdFromUrl;
	}

	@Override
	public boolean isRequestedSessionIdValid() {
		return isSessionIdValid;
	}

	@Override
	public boolean isUserInRole(String arg0) {
		return false;
	}

	private String commaSeparate(String[] strings) {
		StringBuilder sb = new StringBuilder();
		for (String s : strings) {
			if (sb.length() != 0) {
				sb.append(',');
			}
			sb.append(s);
		}
		return sb.toString();
	}

	public void log() {
		for (Field field : getClass().getDeclaredFields()) {
			String name = field.getName();
			try {
				Object value = field.get(this);
				String valueStr = value == null ? "null" : value.toString();
				System.out.println(name + "=" + valueStr);
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

}
